<?php

    /**
     * Coordinator
     *
     * The main class of the core
     *
     * @package      Core
     * @subpackage   Controller
     *
     * @license      GNU Lesser General Public Licence see LICENCE-LGPL file or http://www.gnu.org/licenses/lgpl.html
     */
    class Core_Controller_Coordinator extends Core_Object
    {

	////////////////////////////////////////////////////////////////////////////
	//                                Variables                               //
	////////////////////////////////////////////////////////////////////////////

        /**
         * Singleton instance
         *
         * Marked only as protected to allow extension of the class.
         * To extend, simply override {@link getInstance()}.
         *
         * @var Core_Controller_Coordinator
         */
        protected static $_instance = null;

		/**
		 * Zend_Registry object
		 *
		 * @var Zend_Registry
		 */
		protected $_registry = null;

        /**
         * Request object
         *
         * @var Core_Controller_Request_Abstract
         */
        protected $_request = null;

        /**
         * Response object
         *
         * @var Core_Controller_Response_Abstract
         */
        protected $_response = null;

        /**
         * The selector of the current action
         *
         * @var Core_Selector_Action
         */
        protected $_selector = null;

        /**
         * Plugin list
         *
         * @var  array
         */
        protected $_plugins = array();

        /**
         * Plugin list
         *
         * @var  array
         */
        protected $_orderedPlugins = array();

        /**
         * List of all errors
         *
         * @var array
         */
        protected $_errorMessages = array();

        /**
         * List of all log messages
         *
         * @var array
         */
        protected $_logMessages = array();

        /**
         * The current module name
         *
         * @var string
         */
        protected $_module;

        /**
         * The current action name
         *
         * @var string
         */
        protected $_action;

	////////////////////////////////////////////////////////////////////////////
	//                            Getters / Setters                           //
	////////////////////////////////////////////////////////////////////////////

        /**
         * Returns the current action name
         *
         * @return      string
         */
        public function getAction()
        {
            return $this->_action;
        }

         /**
         * Returns the current module name
         *
         * @return      string
         */
        public function getModule()
        {
            return $this->_module;
        }

        /**
         * Returns the request object
         *
         * @return      Core_Controller_Request_Abstract|null
         */
        public function getRequest()
        {
            return $this->_request;
        }

        /**
         * Sets the request object
         *
         * @value       Core_Controller_Request_Abstract
         * @return      Core_Controller_Coordinator
         */
        public function setRequest(Core_Controller_Request_Abstract $value)
        {
            $this->_request = $value;
            return $this;
        }

        /**
         * Returns the response object
         *
         * @return      Core_Controller_Response_Abstract|null
         */
        public function getResponse()
        {
            return $this->_response;
        }

        /**
         * Sets the response object
         *
         * @value       Core_Controller_Response_Abstract
         * @return      Core_Controller_Coordinator
         */
        public function setResponse(Core_Controller_Response_Abstract $value)
        {
            $this->_response = $value;
            return $this;
        }

        /**
         * Instanciates a response object corresponding to the default
         * response type of the current resquest
         *
         * @throws      Core_Exception
         * @return      Core_Controller_Coordinator
         */
        public function setDefaultResponse()
        {
            if ($this->_request instanceof Core_Controller_Request_Abstract) {
                $this->_response = $this->_request->getResponse();
            } else {
                throw new Core_Exception('Fatal error: request isn\'t an instance of Core_Controller_Request_Abstract');
            }

            return $this;
        }

        /**
         * Returns the selector object
         *
         * @return      Core_Selector_Action|null
         */
        public function getSelector()
        {
            return $this->_selector;
        }

        /**
         * Gets a given plugin if registered
         *
         * @param        string      $pluginName : the name of the plugin
         * @param        boolean     $required : indicates wether the plugin is required or not. If true, will generate an exception if the plugin is not registered.
         * @throws       Core_Exception
		 * @throws		 Core_ArgumentException
		 * @throws		 Core_ArgumentNullException
         * @return       Core_Plugin_Interface|null
         */
        public function getPlugin($pluginName, $required = true)
        {
            if ($pluginName === null) {
				throw new Core_ArgumentNullException('pluginName is a null reference');
			}

            if (strlen(trim($pluginName)) == 0) {
				throw new Core_ArgumentException('pluginName is a zero-length string');
			}

            $plugin     = null;
            $pluginName = strtolower($pluginName);

            if (isset($this->_plugins[$pluginName])) {
                $plugin = $this->_plugins[$pluginName];
            } else {
                if ($required) {
                    throw new Core_Exception('Plugin "'. $pluginName .'" isn\'t registered');
                }
            }

            return $plugin;
        }

        /**
         * Returns the error message stack
         *
         * @return      array
         */
        public function getErrorMessages()
        {
            return $this->_errorMessages;
        }

        /**
         * Returns the log message stack
         *
         * @return      array
         */
        public function getLogMessages()
        {
            return $this->_logMessages;
        }

	////////////////////////////////////////////////////////////////////////////
	//                             Static Methods                             //
	////////////////////////////////////////////////////////////////////////////

        /**
         * Singleton instance
         *
         * @return      Core_Controller_Coordinator
         */
        public static function getInstance()
        {
            if (self::$_instance === null) {
                self::$_instance = new self();
            }

            return self::$_instance;
        }

	////////////////////////////////////////////////////////////////////////////
	//                                 Methods                                //
	////////////////////////////////////////////////////////////////////////////

        /**
         * Constructor
         */
        public function __construct()
		{
            if (PROJECT_TEMP_PATH == '/') {
                die('Error: Application temp directory doesn\'t exist !');
            }

            if (!is_writable(PROJECT_TEMP_PATH)) {
                die('Error: Application temp directory is not writable');
            }

            $this->_registry = Zend_Registry::getInstance();

            // set Error and exception handler
            if ($this->_registry['config']->use_error_handler) {
                $debug = Core_Debug::getInstance($this);
                set_error_handler(array($debug, 'handlerError'));
                set_exception_handler(array($debug, 'handleException'));
            }

			date_default_timezone_set($this->_registry['config']->timezone);

            $this->_loadPlugins();
		}

		/**
		 * Destructor
		 */
		public function __destruct()
		{
			unset($this);
		}

        /**
         * Launches the execution of the action.
         * This method should be called in a entry point
         *
         * @param        Core_Controller_Request_Abstract    $request the request object
         * @throws       Core_Exception
         * @return       void
         */
        public function process(Core_Controller_Request_Abstract $request)
        {
            $this->_request = $request;
            $this->_request->init();

            $sessionParams = array('cookie_path' => $this->_registry['config']->urlengine->basePath);

            if (strcmp(Core_Url::getDomain(), 'localhost') != 0) {
                $sessionParams['cookie_domain'] = Core_Url::getDomain();
            }

            Zend_Session::start($sessionParams);

            $this->_module = $this->_request->getParam('module');
            $this->_action = $this->_request->getParam('action');

            if (empty($this->_module)) {
                $this->_module = $this->_registry['config']->modules->start->name;
            }

            if (empty($this->_action)) {
                if ($this->_module == $this->_registry['config']->modules->start->name) {
                    $this->_action = $this->_registry['config']->modules->start->action;
                } else {
                    $this->_action = $this->_registry['config']->modules->default->action;
                }
            }

            Core_Context::push($this->_module);

            try {
                $this->_selector = new Core_Selector_Action($this->_request->getType(), $this->_module, $this->_action);
                $ctrl = $this->_getController($this->_selector);
            } catch (Exception $e) {
                if (empty($this->_registry['config']->urlengine->noActionFound)) {
                    throw new Core_Exception($e->getMessage());
                }

                try {
                    $this->_selector = new Core_Selector_GenericAction($this->_registry['config']->urlengine->noActionFound);
                    $ctrl = $this->_getController($this->_selector);
                } catch (Exception $e2) {
                    throw new Core_Exception($e->getMessage());
                }
            }

            $pluginparams = array();
            $parameters = $ctrl->getPluginParams();

            if (isset($parameters['*'])) {
                $pluginparams = $parameters['*'];
            }

            if (isset($parameters[$this->_selector->getMethod()])) {
                $pluginparams = array_merge($pluginparams, $parameters[$this->_selector->getMethod()]);
            }

            foreach ($this->_orderedPlugins as $name) {
                $result = $this->_plugins[$name]->beforeAction($pluginparams);

                if ($result instanceof Core_Selector_Action) {
                    $this->_selector = $result;

                    Core_Context::pop();
                    Core_Context::push($result->getModule());

                    $this->_module = $result->getModule();
                    $this->_action = $result->getResource();
                    $ctrl = $this->_getController($this->_selector);
                    //break;

                    if ($name == 'redirect') {
                        $parameters = $ctrl->getPluginParams();

                        if (isset($parameters['*'])) {
                            $pluginparams = $parameters['*'];
                        }

                        if (isset($parameters[$this->_selector->getMethod()])) {
                            $pluginparams = array_merge($pluginparams, $parameters[$this->_selector->getMethod()]);
                        }
                    }
                }
            }

           $this->_response = $ctrl->{$this->_selector->getMethod()}();

            if ($this->_response === null) {
                throw new Core_Exception('Missing response: '. $this->_selector->toString());
            }

            foreach ($this->_orderedPlugins as $name) {
                $this->_plugins[$name]->beforeOutput();
            }

            Core_Meter::getInstance()->markInterval('Displaying page');

            // send response
            if (!$this->_response->output()) {
                $this->_response->outputErrors();
            }

            foreach ($this->_plugins as $name => $obj) {
                $this->_plugins[$name]->afterProcess();
            }

            Core_Context::pop();
            Zend_Session::stop();
        }

        /**
         * Indicates wether a given plugin is enabled
         *
         * @param        string      $pluginName
		 * @throws		 Core_ArgumentException
		 * @throws		 Core_ArgumentNullException
         * @return       boolean     true if the plugin is registered
         */
        public function isPluginEnabled($pluginName)
        {
            if ($pluginName === null) {
				throw new Core_ArgumentNullException('pluginName is a null reference');
			}

            if (strlen(trim($pluginName)) == 0) {
				throw new Core_ArgumentException('pluginName is a zero-length string');
			}

            return isset($this->_plugins[strtolower($pluginName)]);
        }

        /**
         * Stores an error/warning/notice message. Responses object should take care
         * of the errorMessages properties to display them.
         *
         * @param       string      $type : the error type ('error', 'warning', 'notice')
         * @param       integer     $code : the error code
         * @param       string      $message : the error message
         * @param       string      $file : the file name in which the error appeared
         * @param       integer     $line : the line number where the error appeared
         * @return      boolean     true if the the process should stop now, otherwise the error manager does its job
         */
        public function addErrorMessage($type, $code, $message, $file, $line)
        {
            $this->_errorMessages[] = array($type, $code, $message, $file, $line);

            if (!$this->_response) {
                $this->setDefaultResponse();
                return false;
            }

            return !$this->_response->acceptSeveralErrors();
        }

        /**
         * Stores a log message. Responses object should take care
         * of the logMessages properties to display them.
         *
         * @param       string      $message : the error message
         * @param       string      $type : the log type
         * @throws      Core_ArgumentNullException
         * @return      void
         */
        public function addLogMessage($message, $type = 'default')
        {
            if ($message === null) {
				throw new Core_ArgumentNullException('message is a null reference');
			}

            $this->_logMessages[$type][] = $message;
        }

	////////////////////////////////////////////////////////////////////////////
	//                             Private Methods                            //
	////////////////////////////////////////////////////////////////////////////

        /**
         * Loads the plugins and their configuration file
         *
         * @return      void
         */
        private function _loadPlugins()
        {
            foreach ($this->_registry['plugins'] as $name => $path) {
                $this->_loadPlugin($name);
            }
        }

        /**
         * Load the plugin
         *
         * @param       string      $plugin
         * @return      void
         */
        private function _loadPlugin($name)
        {
            if (isset($this->_plugins[strtolower($name)])) {
                return;
            }

            if (!isset($this->_registry['plugins'][$name])) {
                throw new Core_Exception('Fatal error : Dependency must be a valid plugin');
            }

            $path = $this->_registry['plugins'][$name];

            require_once Core_IO_Path::combine($path, $name . '.php');

            $className  = 'Plugin_' . $name;
            $plugin     = new $className();
            $this->_plugins[strtolower($name)] = $plugin;

            if (count($plugin->getDependencies()) > 0) {
                foreach ($plugin->getDependencies() as $dependency) {
                    $this->_loadPlugin($dependency);
                }
            }

            $this->_orderedPlugins[] = strtolower($name);
        }

        /**
         * Gets the controller corresponding to the selector
         *
         * @param       Core_Selector_Action                    $selector
         * @throws      Core_Exception
         * @throws      Core_IO_FileNotFoundException
         * @return      Core_Controller_Action_Abstract
         */
        private function _getController(Core_Selector_Action $selector)
        {
            $ctrlpath = $selector->getPath();

            if (!Core_IO_File::exists($ctrlpath)) {
                throw new Core_IO_FileNotFoundException('Controller file unknow : '. $ctrlpath);
            }

            // Include the controller
            require_once $ctrlpath;

            $class = $selector->getClass();
            if (!class_exists($class, false)) {
                throw new Core_Exception('Class '. $class .' is unknow in : '. $ctrlpath);
            }

            $ctrl = new $class($this->_request);

            if ($ctrl instanceof Core_Controller_Action_Interface) {
                $selector->setMethod(strtolower($_SERVER['REQUEST_METHOD']));
            } else if (!method_exists($ctrl, $selector->getMethod())) {
                throw new Core_Exception('Method '. $selector->getMethod() .' is unknow in controller: '. $ctrlpath . $class);
            }

            return $ctrl;
        }
    }
